package com.cqemme.activitiservice.controller;

import com.cqemme.activitiservice.utils.EmptyChecker;
import com.cqemme.activitiservice.dao.ChartTypeDao;
import com.cqemme.activitiservice.feign.JwtFeign;
import com.cqemme.activitiservice.po.ProcessDefinitionPo;
import com.cqemme.comm.constraint.DefaultValues;
import com.cqemme.comm.po.CurrentLoginUser;
import com.cqemme.comm.po.Result;
import com.cqemme.comm.utils.JackSonUtils;
import com.cqemme.comm.utils.ResultCodeMsg;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Process;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;

import static org.activiti.editor.constants.ModelDataJsonConstants.MODEL_DESCRIPTION;
import static org.activiti.editor.constants.ModelDataJsonConstants.MODEL_NAME;

/**
 * 流程控制器
 * liuzhize 2019年3月7日下午3:28:14
 *
 * @author nejery
 */
@Controller
@Api(tags = "流程图相关")
public class ModelerController {

    private static final Logger logger = LoggerFactory.getLogger(ModelerController.class);

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private HistoryService historyService;
    @Resource
    private RuntimeService runtimeService;
    @Autowired
    private ChartTypeDao chartTypeDao;
    @Resource
    protected ProcessEngine processEngine;

    @Resource
    private ManagementService managementService;


    @Resource
    private JwtFeign jwtFeign;


    /**
     * 获取学校流程图
     *
     * @param currentLoginUser 当前登录用户
     * @return
     */
    @RequestMapping(value = "getAllModel", method = RequestMethod.GET)
    @ApiOperation(value = "获取此学校所有可操作流程图", notes = "nejery")
    @ResponseBody
    public Result getAllModel(
            CurrentLoginUser currentLoginUser,
            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize
    ) {
        String orgId = currentLoginUser.getOrgId() + "";
        Result result = new Result();
        List<Model> list = repositoryService.createNativeModelQuery()
                .sql("select * from " + managementService.getTableName(Model.class)
                        + " m where m.CATEGORY_ in (#{par1}, #{par2})")
                .parameter("par1", DefaultValues.ACTIVITI_NAME_SPACE_PREFIX + orgId)
                .parameter("par2", DefaultValues.ACTIVITI_NAME_SPACE_PREFIX + currentLoginUser.getOrgType())
                .listPage((pageNum - 1) * pageSize, pageSize);

        for (Model model : list) {
            if (!model.getCategory().equals(DefaultValues.ACTIVITI_NAME_SPACE_PREFIX + orgId)) {
                model.setCategory("false");
            }
        }
        result.setStatusCode(ResultCodeMsg.CODE_200);
        result.setData(list);
        return result;
    }


    /**
     * 用作模型显示到文档， 无实际意义，不可删除。
     *
     * @param processDefinitionPo
     */
    @ApiIgnore
    @PostMapping("/asdfasfasfasdf")
    public void voidSfasf(@RequestBody ProcessDefinitionPo processDefinitionPo) {

    }

    /**
     * 跳转编辑器页面 - api
     *
     * @return
     */
    @ApiOperation(value = "编辑流程图", notes = "daibin")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "modelId", value = "模型id", required = true, paramType = "query", dataType = "Integer"),
            @ApiImplicitParam(name = "Authorization", value = "授权Token", required = true, paramType = "query", dataType = "Integer")
    })
    @GetMapping("editorProcess")
    public String editorProcess(@RequestParam(value = "modelId") String modeId, @RequestParam String Authorization) {
        String res = "error";
        Result re = jwtFeign.decodeAuthorization(Authorization);
        String schoolId = ((Map) re.getData()).get("orgId") + "";
        if (EmptyChecker.isEmpty(schoolId)) {
            return "error";
        }

        // 判断模型是否能编辑
        Model model = repositoryService.createModelQuery().modelId(modeId).singleResult();
        if (null != model &&
                model.getCategory().equals(DefaultValues.ACTIVITI_NAME_SPACE_PREFIX + schoolId)
                ) {
            res = "modeler";
        }
        return res;
    }

    /**
     * 创建模型 - api
     *
     * @param response
     * @param name     模型名称
     */
    @ApiOperation(value = "创建模型", notes = "daibin")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "name", value = "模型名称", required = true, paramType = "query", dataType = "String"),
            @ApiImplicitParam(name = "Authorization", value = "token", required = true, paramType = "query", dataType = "String")
    })
    @RequestMapping(value = "/createModel", method = RequestMethod.GET)
    public void createModel(
            HttpServletResponse response,
            @RequestParam String name,
            @RequestParam String Authorization
    ) throws IOException {
        Result re = jwtFeign.decodeAuthorization(Authorization);
        String schoolId = ((Map) re.getData()).get("orgId") + "";
        if (EmptyChecker.isEmpty(schoolId)) {
            return;
        }
        logger.info("创建模型入参name：{},key:{}", name);
        Model model = repositoryService.newModel();
        ObjectNode modelNode = objectMapper.createObjectNode();
        modelNode.put(MODEL_NAME, name);
        modelNode.put(MODEL_DESCRIPTION, "");
        modelNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
        model.setName(name);
        model.setMetaInfo(modelNode.toString());
        model.setCategory(DefaultValues.ACTIVITI_NAME_SPACE_PREFIX + schoolId);
        repositoryService.saveModel(model);
        createObjectNode(model.getId());
        response.sendRedirect("/editorProcess?modelId=" + model.getId() + "&orgId=" + schoolId + "&Authorization=" + Authorization);
        logger.info("创建模型结束，返回模型ID：{}", model.getId());
    }


    @ApiOperation(value = "复制模型", notes = "nejery")
    @GetMapping("copyModel")
    @ResponseBody
    public Result copyModel(CurrentLoginUser currentLoginUser,
                            @RequestParam(value = "modelId") String modelId,
                            @RequestParam(value = "name") String name
    ) throws IOException {
        Result result = new Result();
        Model sourceModel = repositoryService.getModel(modelId);
        ObjectNode sourceObjectNode = (ObjectNode) new ObjectMapper()
                .readTree(repositoryService.getModelEditorSource(modelId));

        ObjectNode modelJson = (ObjectNode) objectMapper.readTree(sourceModel.getMetaInfo());
        modelJson.put(MODEL_NAME, name);
        modelJson.put("revision", 1);

        Model model = repositoryService.newModel();
        model.setMetaInfo(JackSonUtils.beanToString(modelJson));
        model.setName(name);
        model.setCategory(DefaultValues.ACTIVITI_NAME_SPACE_PREFIX + currentLoginUser.getOrgId());
        model.setVersion(1);
        repositoryService.saveModel(model);

        ObjectNode editorNode = sourceObjectNode.deepCopy();
        ObjectNode properties = objectMapper.createObjectNode();
        properties.put("process_author", "amkj");
        properties.put("name", model.getName());
        editorNode.set("properties", properties);

        repositoryService.addModelEditorSource(model.getId(), editorNode.toString().getBytes("utf-8"));

        return result;
    }


    /**
     * 创建模型时完善ModelEditorSource
     *
     * @param modelId
     */
    private void createObjectNode(String modelId) {
        logger.info("创建模型完善ModelEditorSource入参模型ID：{}", modelId);
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.put("stencilset", stencilSetNode);
        try {
            repositoryService.addModelEditorSource(modelId, editorNode.toString().getBytes("utf-8"));
        } catch (Exception e) {
            logger.info("创建模型时完善ModelEditorSource服务异常：{}", e);
        }
        logger.info("创建模型完善ModelEditorSource结束");
    }


    /**
     * 发布流程 - api
     *
     * @param json
     * @return
     */
    @ResponseBody
    @ApiOperation(value = "发布流程图", notes = "daibin")
    @ApiImplicitParam(name = "json", value = "类似于这样传 -> {chartType:'流程图类型 - sos报警值应为：sos-security',modelId:'模型id'}", required = true)
    @RequestMapping(value = "/releaseProcess", method = RequestMethod.POST)
    public Result releaseProcess(
            CurrentLoginUser currentLoginUser,
            @RequestBody Map<String, String> json
    ) {
        //以后前端需要传参的 modelId
        String schoolId = currentLoginUser.getOrgId() + "";
        String modelId = json.get("modelId");

        logger.info("流程部署入参modelId：{}", modelId);
        Result result = new Result();
        try {
            //更新 学校和流程图模型 关系数据
            Model modelData = repositoryService.getModel(modelId);
            byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
            if (bytes == null) {
                logger.error("部署ID:{}的模型数据为空，请先设计流程并成功保存，再进行发布", modelId);
                result.setStatusCode(ResultCodeMsg.CODE_204);
                result.setMessage("模型图错误！");
                return result;
            }
            JsonNode modelNode = new ObjectMapper().readTree(bytes);
            BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
            Process modelProcess = model.getMainProcess();

            //改变启动key为源key+学校id
            model.getMainProcess().setId(modelProcess.getId());
            model.setTargetNamespace(DefaultValues.ACTIVITI_NAME_SPACE_PREFIX + schoolId);
            //部署
            Deployment deployment = repositoryService.createDeployment()
                    .name(modelData.getName() + ".bpmn")
                    .addBpmnModel(modelData.getKey() + ".bpmn20.xml", model)
                    .deploy();
            modelData.setDeploymentId(deployment.getId());
            repositoryService.saveModel(modelData);
        } catch (Exception e) {
            logger.info("部署modelId:{}模型服务异常：{}", modelId, e);
            result.setStatusCode(ResultCodeMsg.CODE_204);
            result.setMessage("部署模型服务异常");
        }
        return result;
    }

    @ResponseBody
    @ApiOperation(value = "已经发布的流程图", notes = "nejery")
    @GetMapping("/getAllDeployment")
    public Result getAllDeployment(
            CurrentLoginUser currentLoginUser,
            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize
    ) {
        Result result = new Result();
        List<Model> depModelList=repositoryService.createModelQuery()
                .deployed()
                .modelCategory(DefaultValues.ACTIVITI_NAME_SPACE_PREFIX + currentLoginUser.getOrgId())
                .listPage((pageNum - 1) * pageSize, pageSize);
        Set<String> modelIds=new HashSet<>();
        for (Model depModel : depModelList){
            modelIds.add(depModel.getDeploymentId());
        }
        List<ProcessDefinition> processDefinitions=repositoryService.createProcessDefinitionQuery()
                .deploymentIds(modelIds)
                .list();
//        List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery()
//                .processDefinitionCategory(DefaultValues.ACTIVITI_NAME_SPACE_PREFIX + currentLoginUser.getOrgId())
//                .orderByProcessDefinitionVersion().desc()
//                .latestVersion()
//                .listPage((pageNum - 1) * pageSize, pageSize);
        List<ProcessDefinitionPo> resList = new ArrayList<>();
        ProcessDefinitionPo processDefinitionPo;
        for (ProcessDefinition processDefinition : processDefinitions) {
            processDefinitionPo = new ProcessDefinitionPo();
            BeanUtils.copyProperties(processDefinition, processDefinitionPo);
            resList.add(processDefinitionPo);
        }
        result.setData(resList);
        return result;
    }


    /**
     * 撤销流程部署
     *
     * @param modelId 模型ID
     * @param
     * @return
     */
    @ApiIgnore
    @ResponseBody
    @RequestMapping("/revokePublish")
    public Result revokePublish(String modelId, CurrentLoginUser currentLoginUser) {
        logger.info("撤销发布流程入参modelId：{}", modelId);
        Result result = new Result();
        Model modelData = repositoryService.getModel(modelId);
        if (null != modelData &&
                modelData.getCategory()
                        .equals(DefaultValues.ACTIVITI_NAME_SPACE_PREFIX + currentLoginUser.getOrgId())) {
            try {
                /**
                 * 参数不加true:为普通删除，如果当前规则下有正在执行的流程，则抛异常
                 * 参数加true:为级联删除,会删除和当前规则相关的所有信息，包括历史
                 */
                if (EmptyChecker.notEmpty(modelData.getDeploymentId())) {
                    repositoryService.deleteDeployment(modelData.getDeploymentId(), false);
                }

            } catch (Exception e) {
                result.setStatusCode(ResultCodeMsg.CODE_204);
                result.setMessage("撤销流程失败");
                logger.error("撤销已部署流程服务异常：{}", e);
            }
        }
        return result;
    }


    /**
     * 删除流程 - api
     *
     * @param
     * @return
     */
    @ApiOperation(value = "删除流程", notes = "daibin")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "modelId", value = "模型id", required = true, paramType = "query", dataType = "String")
    })
    @ResponseBody
    @RequestMapping(value = "/deleteProcess", method = RequestMethod.DELETE)
    public Object deleteProcess(
            CurrentLoginUser currentLoginUser,
            @RequestBody Map<String, Object> json
    ) {
        String schoolId = currentLoginUser.getOrgId() + "";
        String modelId = String.valueOf(json.get("modelId"));
        Model modelData = repositoryService.createModelQuery().modelId(modelId).singleResult();
        Result result = new Result();
        if (modelData.getCategory().equals(DefaultValues.ACTIVITI_NAME_SPACE_PREFIX + schoolId)) {
            Result result1 = revokePublish(modelId, currentLoginUser);
            if (result1.getStatusCode() == 200) {
                repositoryService.deleteModel(modelId);
            } else {
                result.setStatusCode(ResultCodeMsg.CODE_204);
                result.setMessage("删除失败!");
                return result;
            }
            try {
                if (EmptyChecker.notEmpty(modelData.getKey())) {
                    List<ProcessInstance> piList = runtimeService.createProcessInstanceQuery().processDefinitionKey(modelData.getKey()).list();
                    for (ProcessInstance pi : piList) {
                        runtimeService.deleteProcessInstance(pi.getId(), "");
                        historyService.deleteHistoricProcessInstance(pi.getId());
                    }
                }
                result.setStatusCode(ResultCodeMsg.CODE_200);
                result.setMessage("删除成功");
            } catch (Exception e) {
                logger.error("删除流程实例服务异常：{}", e);
                result.setStatusCode(ResultCodeMsg.CODE_204);
                result.setMessage("删除失败");
            }
        } else {
            result.setStatusCode(ResultCodeMsg.CODE_204);
            result.setMessage("你不具有删除权限");
        }

        return result;
    }

    /**
     * 得到所有的流程图类型
     *
     * @return
     */
    @ApiOperation(value = "获取所有的流程图类型", notes = "daibin")
    @RequestMapping(value = "/getChartType", method = RequestMethod.GET)
    @ResponseBody
    public Result getChartType() {
        Result result = new Result();
        result.setStatusCode(ResultCodeMsg.CODE_200);
        result.setData(chartTypeDao.selectList(null));
        return result;
    }

    /**
     * 查看流程图
     */
    @ApiOperation(value = "获取流程完成实时图", notes = "这种调用：img src=\"http://127.0.0.1:90/activity/image?processInstanceId=2501\"")
    @RequestMapping(value = "/image", method = RequestMethod.GET)
    public void image(HttpServletResponse response,
                      @RequestParam String processInstanceId) {
        try {
            InputStream is = getDiagram(processInstanceId);
            if (is == null) {
                return;
            }
            response.setContentType("image/png");
            BufferedImage image = ImageIO.read(is);
            OutputStream out = response.getOutputStream();
            ImageIO.write(image, "png", out);

            is.close();
            out.close();
        } catch (Exception ex) {

        }
    }


    public InputStream getDiagram(String processInstanceId) {
        //获得流程实例
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        String processDefinitionId = StringUtils.EMPTY;
        if (processInstance == null) {
            //查询已经结束的流程实例
            HistoricProcessInstance processInstanceHistory =
                    historyService.createHistoricProcessInstanceQuery()
                            .processInstanceId(processInstanceId).singleResult();
            if (processInstanceHistory == null) {
                return null;
            } else {
                processDefinitionId = processInstanceHistory.getProcessDefinitionId();
            }
        } else {
            processDefinitionId = processInstance.getProcessDefinitionId();
        }

        //使用宋体
        String fontName = "宋体";
        //获取BPMN模型对象
        BpmnModel model = repositoryService.getBpmnModel(processDefinitionId);
        //获取流程实例当前的节点，需要高亮显示
        List<String> currentActs = Collections.EMPTY_LIST;
        if (processInstance != null) {
            currentActs = runtimeService.getActiveActivityIds(processInstance.getId());
        }

        return processEngine.getProcessEngineConfiguration()
                .getProcessDiagramGenerator()
                .generateDiagram(model, "png", currentActs, new ArrayList<String>(),
                        fontName, fontName, fontName, null, 1.0);
    }
//    generateDiagram 方法参数说明
//    bpmnModel：bpmnModel对象
//
//    imageType：生成的图像格式，我用的是png
//
//    highLightedActivities：要高亮显示的活动的节点
//
//    highLightedFlows：高亮显示的连线
//
//    activityFontName：activity字体，默认字体是Arial，如果你的流程图中使用了中文，那么会显示乱码
//
//    labelFontName：label字体，同时
//
//    annotationFontName：annotation字体，同上
//
//    customClassLoader：提供一个自定义类加载器来检索图标图像
//
//    scaleFactor：比例因子，试着改了一下，会改变连线箭头的大小，一般设置为1.0

}
